<template>
  <div class="g-app">
    <h2>ts的类型</h2>
    <h2>数组与元组的区别：数组元素只能有一种类型，元祖元素可以有多种类型</h2>
    <p>list1:{{ list1 }}</p>
    <p>list2:{{ list2 }}</p>
    <p>元组：{{ x }}</p>
    <hr />
    <h2>枚举 enum 是什么？有什么使用场景？</h2>
    <p>enum 枚举，一般用于表示有限的一些选项，例如使用 enum 定义 4 个方向</p>
    <p>{{ Direction.Down }}</p>
  </div>
</template>
<script lang="ts" setup>
// 数组，两种定义方式
const list1: number[] = [1, 2, 3];
const list2: Array<string> = ["a", "b", "c"];

// 元组
let x: [string, number] = ["x", 10];
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

/**
 * keyof 和typeof 有什么区别？
 *
 * typeof 是 JS 基础用法，用于获取类型，这个很简单。
 * keyof 是 TS 语法，用于获取所有 key 的类型，例如
 */
interface Person {
  name: string;
  age: number;
  location: string;
}

type PersonType = keyof Person;
const person: PersonType = "age"; // person 只能是'name' | 'age' | 'location'这三个字符串中的一个
// 等价于 type PersonType = 'name' | 'age' | 'location'

/**
 * 什么是泛型，如何使用它？
 *
 * 泛型 Generics 即通用类型，可以灵活的定义类型而无需写死。
 */
const list: Array<string> = ["a", "b"];
const numbers: Array<number> = [10, 20];

interface User {
  name: string;
  age: number;
}
// 泛型通常的写法是 <T> T表示要传入的类型
const userList: Array<User> = [{ name: "x", age: 20 }];

// 用于函数
// Type 一般可简写为 T
function fn1<Type>(arg: Type): Type {
  return arg;
}
// 将string类型传给了Type
const x1 = fn1<string>("xxx");

// 可以有多个泛型，名称自己定义
function fn2<T, K>(a: T, b: K) {
  console.log(a, b);
}
fn2<string, number>("x", 10);

// 用于class
class SomeClass<T> {
  name: T;
  constructor(name: T) {
    this.name = name;
  }
  getName(): T {
    return this.name;
  }
}
const s1 = new SomeClass<String>("xx");
// 用于 type
function fn<T>(arg: T): T {
  return arg;
}

const myFn: <U>(arg: U) => U = fn; // U T 随便定义

// 用于interface
// interface F1 {
//   <T>(arg: T): T;
// }
interface F1<T> {
  (arg: T): T; // 这是一个函数，参数类型由输入决定，并且返回值跟输入值一致
}
function fn4<T>(arg: T): T {
  return arg;
}
const myFn3: F1<number> = fn4;
// myFn3('5445') 报错
myFn3(45454);

/**
 * 交叉类型和联合类型
 *
 * 交叉类型：将多个类型合并为一个类型，包含了所需的所有类型的特性。例如 T1 & T2 & T3
 * 联合类型：一种“或”的关系。格式如 T1 | T2 | T3。代码示例如下
 */
interface U1 {
  name: string;
  city: string;
}
interface U2 {
  name: string;
  age: number;
}
type UserType1 = U1 & U2;
const userA: UserType1 = { name: "x", age: 20, city: "beijing" };

// 可在 userA 获取所有属性，相当于“并集”
userA.name;
userA.age;
userA.city;
// 基础类型无法交叉，会返回never
type T4 = string & number // never


interface U3 {
  name: string
  city: string
}
interface U4 {
  name: string
  age: number
}

function fn5(): U3 | U4 {
  return {
    name: 'x',
    age: 20,
    city: "北京",
    // love: "吃饭" // 这里报错了 涉及的知识点如下
  }
}
// 直接字面量
// TypeScript 对 ​直接赋值的对象字面量 会严格检查所有属性是否属于联合类型的某个成员。
const obj: U3 | U4 = {
  name: 'x',
  age: 20,
  city: "北京",
  // gender: "male" // ❌ 类型错误：对象字面量不能包含未知属性
};
// 间接赋值
// 通过中间变量赋值时，TypeScript ​仅检查结构兼容性，不限制冗余属性。
const tmp = {
  name: 'x',
  age: 20,
  city: "北京",
  gender: "male"
};
const obj2: U3 | U4 = tmp; // ✅ 正确

const result = fn5()
console.log('联合类型', result)// {name: 'x', age: 20}
</script>
